#include "stdafx.h"
#include "object3d.h"


myObject3D::myObject3D(void)
{
	material = NULL;
	n = 0;
	m = 0;
}

myObject3D::~myObject3D(void)
{
	if( material != NULL)
		delete material;
}

void myObject3D::init()
{
	//normalize();
	if(normals.size() == 0)
		computeNormals();
	if(textures.size() == 0)
		computeSphereTexture();
	computeTangents();
	createObjectBuffers();
}

void myObject3D::normalize()
{
	int i;
	int tmpxmin = 0, tmpymin = 0, tmpzmin = 0, tmpxmax = 0, tmpymax = 0, tmpzmax = 0;

	for (i=0;i<n;i++) {
		if (vertices[3*i] < vertices[3*tmpxmin]) tmpxmin = i;
		if (vertices[3*i] > vertices[3*tmpxmax]) tmpxmax = i;

		if (vertices[3*i+1] < vertices[3*tmpymin+1]) tmpymin = i;
		if (vertices[3*i+1] > vertices[3*tmpymax+1]) tmpymax = i;

		if (vertices[3*i+2] < vertices[3*tmpzmin+2]) tmpzmin = i;
		if (vertices[3*i+2] > vertices[3*tmpzmax+2]) tmpzmax = i;
	}

	double xmin = vertices[3*tmpxmin], xmax = vertices[3*tmpxmax], 
			ymin = vertices[3*tmpymin+1], ymax = vertices[3*tmpymax+1], 
			zmin = vertices[3*tmpzmin+2], zmax = vertices[3*tmpzmax+2];

	double scale = (xmax-xmin) <= (ymax-ymin) ? (xmax-xmin) : (ymax-ymin);
	//double scale = fmin( (xmax-xmin), (ymax-ymin) );
	scale = scale >= (zmax-zmin) ? scale : (zmax-zmin);
	//scale = fmax(scale, (zmax-zmin));

	for (i=0;i<n;i++) {
		vertices[3*i] -= (xmax+xmin)/2;
		vertices[3*i+1] -= (ymax+ymin)/2;
		vertices[3*i+2] -= (zmax+zmin)/2;

		vertices[3*i] /= scale;
		vertices[3*i+1] /= scale;
		vertices[3*i+2] /= scale;
	}
}

void myObject3D::computeNormal(int v1, int v2, int v3, float & x, float & y, float & z)
{
	double dx1 = vertices[v2*3] - vertices[v1*3];
	double dx2 = vertices[v3*3] - vertices[v2*3];
	double dy1 = vertices[v2*3+1] - vertices[v1*3+1];
	double dy2 = vertices[v3*3+1] - vertices[v2*3+1];
	double dz1 = vertices[v2*3+2] - vertices[v1*3+2];
	double dz2 = vertices[v3*3+2] - vertices[v2*3+2];


	double dx = dy1 * dz2 - dz1 * dy2;
	double dy = dz1 * dx2 - dx1 * dz2;
	double dz = dx1 * dy2 - dy1 * dx2;

	double length = sqrt(dx*dx + dy*dy + dz*dz);

	x = dx/length;
	y = dy/length;
	z = dz/length;
}

void myObject3D::computeNormals( )
{
	int i, j, k;
	float x1, y1, z1;
	
	normals.clear();
	normals.resize(3*n);

	int *incidences = new int[n];
	for (i=0;i<3*n;i++) normals[i] = 0.0;
	for (i=0;i<n;i++) incidences[i] = 0;

	for (j=0;j<m;j++)
	{
		computeNormal(indices[3*j], indices[3*j+1], indices[3*j+2], x1, y1, z1);
		normals[3*indices[3*j]] += x1; normals[3*indices[3*j]+1] += y1; normals[3*indices[3*j]+2] += z1;
		normals[3*indices[3*j+1]] += x1; normals[3*indices[3*j+1]+1] += y1; normals[3*indices[3*j+1]+2] += z1;
		normals[3*indices[3*j+2]] += x1; normals[3*indices[3*j+2]+1] += y1; normals[3*indices[3*j+2]+2] += z1;
		incidences[indices[3*j]]++; incidences[indices[3*j+1]]++; incidences[indices[3*j+2]]++;
	}
	for (i=0;i<n;i++)
		if (incidences[i]!=0) 
		{
			normals[3*i] /= incidences[i]; normals[3*i+1] /= incidences[i]; normals[3*i+2] /= incidences[i];
		}
}

void myObject3D::createObjectBuffers()
{
	glGenBuffers(5, buffers);
	
	glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
	glBufferData(GL_ARRAY_BUFFER, n*3*4, &vertices[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[1]) ; 
	glBufferData(GL_ARRAY_BUFFER, n*3*4, &colors[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[3]) ; 
	glBufferData(GL_ARRAY_BUFFER, n*3*4, &normals[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[4]) ; 
	glBufferData(GL_ARRAY_BUFFER, n*2*4, &textures[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[5]) ; 
	glBufferData(GL_ARRAY_BUFFER, n*3*4, &tangents[0], GL_STATIC_DRAW);
 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, m*3*4, &indices[0], GL_STATIC_DRAW);
}

void myObject3D::displayObject()
{
	glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
	glVertexPointer(3, GL_FLOAT, 0, 0);
	glEnableClientState(GL_VERTEX_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
	glColorPointer(3, GL_FLOAT, 0, 0); 
	glEnableClientState(GL_COLOR_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[3]);
	glNormalPointer(GL_FLOAT, 0, 0);
	glEnableClientState(GL_NORMAL_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[4]);
	glTexCoordPointer(2, GL_FLOAT, 0, 0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, buffers[5]);
	glVertexAttribPointer(ShaderManager::loc_tangent, 3, GL_FLOAT,GL_FALSE, 0, 0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2]);

	if ( groups.size() == 0 )
	{
		if ( material != NULL )
			material->use();		

		glDrawElements(GL_TRIANGLES, m*3, GL_UNSIGNED_INT, 0);

		if ( material != NULL )
			material->unuse();
	}
	else
	{
		for (int i = 0; i < groups.size() ; i++)
		{
			if ( groups[i]->material != NULL)
				groups[i]->material->use();

			glDrawElements(GL_TRIANGLES, (groups[i]->indexEnd - groups[i]->indexStart + 1) * 3, GL_UNSIGNED_INT, (void*)(groups[i]->indexStart * 3 * sizeof(GLuint)));

			if ( groups[i]->material != NULL)
				groups[i]->material->unuse();
		}
	}
	

}

void myObject3D::displayNormals()
{
	double x = 0.05;

	glColor3f(0.0f,1.0f,0.0f); 
	for (int i=0;i<n;i++)
	{
		glBegin( GL_LINES ); 
			glVertex3f( vertices[i*3], vertices[i*3+1], vertices[i*3+2] ); 
			glVertex3f( vertices[i*3]  + normals[i*3]*x, vertices[i*3+1]  + normals[i*3+1]*x, vertices[i*3+2]  + normals[i*3+2]*x); 
		glEnd();
	}
}

void myObject3D::computeCylinderTexture()
{
	textures.clear();
	textures.resize(2*n);
	GLfloat x, y, z;
	for (int i=0;i<n;i++)
	{
		x = vertices[3*i]; y = vertices[3*i+1]; z = vertices[3*i+2];
	
		textures[2*i] = z;
		if ( y>=0.0f )     textures[2*i+1] = atan2(  y,  x ) / (PI) ;
		else if ( y<0.0f )  textures[2*i+1] = (- atan2(  y,  x )) / (PI) ;
		//this has problems at the seam, when 1->0 and so interpoltion results in the whole image squeezed between the two border vertices.
		//if ( y>=0.0f )     textures[2*i+1] = atan2(  y,  x ) / (2*PI) ;
		//else if ( y<0.0f )  textures[2*i+1] = (2*PI + atan2(  y,  x )) / (2*PI) ;
	}
}

void myObject3D::computeSphereTexture()
{
	textures.clear();
	textures.resize(2*n);
	GLfloat x, y, z;
	for (int i=0;i<n;i++)
	{
		x = vertices[3*i]; y = vertices[3*i+1]; z = vertices[3*i+2];
	
		textures[2*i] = 0.5 +  ( atan2( z ,  x ) / (2 * PI) ) ;
		textures[2*i+1] = 0.5 + ( asin( y ) / (PI) ) ;
	}
}

void myObject3D::computeTangent(int v0, int v1, int v2, float & x, float & y, float & z)
{
	float du1 = textures[2*v1] - textures[2*v0] ;
	float dv1 = textures[2*v1+1] - textures[2*v0+1] ;
	float du2 = textures[2*v2] - textures[2*v0] ;
	float dv2 = textures[2*v2+1] - textures[2*v0+1];

	float f = 1.0f / (du1 * dv2 - du2 * dv1);
	if ( (du1*dv2 - du2*dv1) == 0){
		x = y = z = 0; return;
	}

	float e1x = vertices[3*v1] - vertices[3*v0];
	float e1y = vertices[3*v1+1] - vertices[3*v0+1];
	float e1z = vertices[3*v1+2] - vertices[3*v0+2];

	float e2x = vertices[3*v2] - vertices[3*v0];
	float e2y = vertices[3*v2+1] - vertices[3*v0+1];
	float e2z = vertices[3*v2+2] - vertices[3*v0+2];

	x = f * ( dv2 * e1x - dv1 * e2x );
	y = f * ( dv2 * e1y - dv1 * e2y );
	z = f * ( dv2 * e1z - dv1 * e2z );
}

void myObject3D::computeTangents( )
{
	int i, j, k;
	GLfloat x1, y1, z1;

	int n = vertices.size()/3;
	int m = indices.size()/3;

	tangents.clear();
	tangents.resize(3*n);

	int *incidences = new int[n];
	for (i=0;i<3*n;i++) tangents[i] = 0.0;
	for (i=0;i<n;i++) incidences[i] = 0;

	for (j=0;j<m;j++)
	{
		computeTangent(indices[3*j], indices[3*j+1], indices[3*j+2], x1, y1, z1);
		tangents[3*indices[3*j]] += x1; tangents[3*indices[3*j]+1] += y1; tangents[3*indices[3*j]+2] += z1;
		tangents[3*indices[3*j+1]] += x1; tangents[3*indices[3*j+1]+1] += y1; tangents[3*indices[3*j+1]+2] += z1;
		tangents[3*indices[3*j+2]] += x1; tangents[3*indices[3*j+2]+1] += y1; tangents[3*indices[3*j+2]+2] += z1;
		incidences[indices[3*j]]++; incidences[indices[3*j+1]]++; incidences[indices[3*j+2]]++;
	}
	for (i=0;i<n;i++) {
		float l = sqrt( tangents[3*i]*tangents[3*i] + tangents[3*i+1]*tangents[3*i+1] + tangents[3*i+2]*tangents[3*i+2] );
		tangents[3*i] /= l; tangents[3*i+1] /= l; tangents[3*i+2] /= l;
	}
}